package gu.sql2java;

import static com.google.common.base.Preconditions.*;
import static gu.sql2java.SimpleLog.*;

import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import gu.sql2java.TableManager.Action;
import gu.sql2java.exception.ObjectRetrievalException;
import gu.sql2java.exception.RuntimeDaoException;

class TableCache<B extends BaseBean> extends ColumnCache<B> 
	implements TableListener<B>{
	/** 执行缓存更新的单线程池对象 */
	private static final ExecutorService executor = MoreExecutors.getExitingExecutorService(
			new ThreadPoolExecutor(1, 1,
	                0L, TimeUnit.MILLISECONDS,
	                new LinkedBlockingQueue<Runnable>(),
	                new ThreadFactoryBuilder().setNameFormat("sql2java-cache-updater-%d").build()));
    private final Map<String, IKeyCache<B>> indexCachers;
	/**
	 * constructor<br>
	 * @param metaData meta data for table 
     * @param updateStrategy cache update strategy,{@link Constant#DEFAULT_STRATEGY} be used if {@code null}
     * @param maximumSize maximum capacity of cache ,{@link Constant#DEFAULT_CACHE_MAXIMUMSIZE } be used if {@code null} or <=0,see also {@link CacheBuilder#maximumSize(long)}
     * @param duration cache data expired time,{@link Constant#DEFAULT_DURATION} be used if {@code null} or <=0,see also {@link CacheBuilder#expireAfterAccess(long, TimeUnit)}
     * @param unit time unit for {@code duration},{@link Constant#DEFAULT_TIME_UNIT} be used if {@code null} ,see also {@link CacheBuilder#expireAfterAccess(long, TimeUnit)}
	 */
	public TableCache(RowMetaData metaData,UpdateStrategy updateStrategy,Long maximumSize, Long duration, TimeUnit unit) {
		super(metaData,null,updateStrategy,maximumSize, duration, unit);
		ImmutableMap.Builder<String, IKeyCache<B>> builder = ImmutableMap.builder();
		// 索引缓存对象的更新策略
		UpdateStrategy indexStrategy = UpdateStrategy.refresh.equals(this.updateStrategy) 
				? UpdateStrategy.always 
				: this.updateStrategy;
		for(IndexMetaData index : metaData.getUniqueIndices().values()){
			builder.put(
					index.name, 
					new ColumnCache<B>(
									this.metaData,
									index.name,
									indexStrategy,
									this.maximumSize,
									this.duration,
									this.unit));
		}
		indexCachers = builder.build();
		manager.bindForeignKeyListenerForDeleteRule();
	}
	/** 注册侦听器 */
    public void registerListener() {        
        manager.registerListener(this);
        if(debug){
        	log("REGISTER LISTENER FOR INDEX AND PRIMARY KEY of " + metaData.tablename);
        }
    }
    /** 注销侦听器 */
    public void unregisterListener() {
    	manager.unregisterListener(this);
    }
    
    /**
     * force update bean to all caches that excluding specified by {@link #recursive}
     * @param bean
     */
    private void updateAll(B bean){
    	update(bean, UpdateStrategy.always);
    	for(IKeyCache<B> cache : indexCachers.values()){
    		cache.update(bean, UpdateStrategy.always);
    	}
    }
    /**
     * return record (B) that unique indexed by 'keys'
     * @param indexName index name 
     * @param keys values for index key 
     * @return B instance if found
     * @throws ObjectRetrievalException not found record
     */
    public B getBeanByIndex(String indexName,Object... keys)throws ObjectRetrievalException{
		return checkNotNull(indexCachers.get(indexName),"INVALID indexName %s",indexName).getBean(keys);
	}
	/**
	 * return record (B) that unique indexed by 'keys'
     * @param indexName index name 
     * @param keys values for index key 
     * @return B instance if found,or null
	 */
	public B getBeanByIndexUnchecked(String indexName,Object... keys){
		return checkNotNull(indexCachers.get(indexName),"INVALID indexName %s",indexName).getBeanUnchecked(keys);
	}
	
	/**
	 * wrap the 'action' for updating cache while retrieve data from database 
	 * @param action
	 * @return {@link Action} instance
	 */
	public Action<B> wrap(Action<B> action){
		if(action == null
				|| action instanceof TableCache.CacheWrapper
				|| action instanceof BaseTableManager.DeleteBeanAction){
			return action;
		}
		return new CacheWrapper(action);
	}
	
	//////////////////////////////////////////////
    // TableListener IMPLEMENTATION
	//////////////////////////////////////////////
	
	@Override
    public void afterUpdate(B bean) {
		if(!UpdateStrategy.refresh.equals(updateStrategy)){
			// 从数据库获取完整数据记录
			// 更新策略为refresh时,update方法会调用loadfromDatabase从数据库更新所以这里不需要执行
			bean = loadfromDatabase(bean.primaryValues());
		}
        update(bean);
		if(UpdateStrategy.refresh.equals(updateStrategy)){
			// 从主键cache中获取完整数据记录
			bean = getBean(bean.primaryValues());
		}
        for(IKeyCache<B> cache : indexCachers.values()){
        	cache.update(bean);
        }
    }
    
    @Override
    public void afterInsert(B bean) {
        update(bean);
        for(IKeyCache<B> cache : indexCachers.values()){
        	cache.update(bean);
        }
    }
        
	@Override
    public void afterDelete(B bean) {
        B mem = remove(bean);
        for(IKeyCache<B> cache : indexCachers.values()){
        	if(cache.hasValidKey(bean)){
        		cache.remove(bean);
        	}else if(cache.hasValidKey(mem)){
        		cache.remove(mem);
        	}
        }
    }
	
	@Override
	public void beforeInsert(B bean) throws RuntimeDaoException {}
	@Override
	public void beforeUpdate(B bean) throws RuntimeDaoException {}
	@Override
	public void beforeDelete(B bean) throws RuntimeDaoException {}
	@Override
	public void done() throws RuntimeDaoException {}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + ((indexCachers == null) ? 0 : indexCachers.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!super.equals(obj)) {
			return false;
		}
		if (!(obj instanceof TableCache)) {
			return false;
		}
		TableCache<?> other = (TableCache<?>) obj;
		if (indexCachers == null) {
			if (other.indexCachers != null) {
				return false;
			}
		} else if (!indexCachers.equals(other.indexCachers)) {
			return false;
		}
		return true;
	}
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("TableCache [super=");
		builder.append(super.toString());
		builder.append(",indexCachers=");
		builder.append(indexCachers);
		builder.append("]");
		return builder.toString();
	}
    private class CacheWrapper implements Action<B>{
        private final Action<B> action;
        public CacheWrapper(Action<B>action){
            this.action = checkNotNull(action,"action is null");
        }
        @Override
        public void call(final B bean) {
            action.call(bean);            
            // 异步执行缓存更新 
            executor.execute(new Runnable() {
				
				@Override
				public void run() {
					updateAll(bean);					
				}
			});
        }
    }
}
